'use strict';
/** This is the init setting of game. */
const canvas = document.getElementById("game-canvas");
const score_element = document.getElementById("game-score");
const snake_body_color = "#cf2128";
const snake_head_color = "#ef3148";
const food_color = "#1faf0f";
const default_head_pos = [11, 10];
const default_body_len = 1;
const default_direct = 'r';
const difficulty = 200;
const map_width = 20;
const move_key = {
    'r':{
        'key': 'x',
        'step': 1
    },
    'l':{
        'key': 'x',
        'step': -1
    },
    'u':{
        'key': 'y',
        'step': -1
    },
    'd':{
        'key': 'y',
        'step': 1
    }
}
const direct_key = {
    'ArrowRight':'r',
    'ArrowLeft':'l',
    'ArrowUp':'u',
    'ArrowDown':'d',
    'w':'u',
    'a':'l',
    'd':'r',
    's':'d'
}
const prevent_key = {
    'r':'l',
    'l':'r',
    'u':'d',
    'd':'u'
}

/** The Game Attribute Model */
function Game_Attr(a, b, c) {
    this.snake_body_color = a;
    this.snake_head_color = b;
    this.food_color = c;
}

/** The Vec model(To mark position and direction) */
class Vec{
    constructor(x, y){
        this.x = x?x:0, this.y = y?y:0;
    }
    copy(a){
        this.x = a.x, this.y = a.y;
    }
    equal(a){
        return this.x == a.x&&this.y == a.y;
    }
    //Other methods... (Maybe will be created in the future)
}

/** The Snake Model */
class Snake{
    constructor(){
        this.body_units = new Array();
        this.head = new Vec(default_head_pos[0], default_head_pos[1]);
    }
}

/** The Game State Model */
function Game_State() {
    this.direction = default_direct; //Snake's direction (left: l |right: r |up: u |down: d)
    this.score = 0;
    this.snake = new Snake();
    this.food_pos = new Vec();
    this.key_queue = new Array();
}

/** The renderer class */
class Renderer{
    constructor(canvas, snake){
        //TODO: The renderer class should be finished.
        this.canvas = canvas;
        this.canvas_ctx = canvas.getContext("2d");
    }
    render(snake, score, food_pos){
        this.clean();
        this.drawSnake(snake);
        this.drawFood(food_pos);
        //render score
        score_element.innerText = score;
    }
    drawSnake(snake){
        //draw head
        this.createBlock(snake.head, snake_head_color);
        let a = snake.body_units;
        for(let i = 0; i < a.length; i++)this.createBlock(a[i], snake_body_color);
    }
    drawFood(pos) {
        this.createBlock(pos, food_color);
    }
    createBlock(pos, color){
        let bx = this.canvas.width/map_width, by = this.canvas.height/map_width;
        this.canvas_ctx.fillStyle=color;
        this.canvas_ctx.fillRect(pos.x*bx, pos.y*by, bx, by);
    }
    clean() {
        this.canvas_ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
    //...
}

/** The Main Game Class */
class Game{
    constructor() {
        this.canvas = canvas;
        this.attribute;
        this.state;
        this.renderer = new Renderer(canvas);
        this.interval;
        this.Load();
    }
    /** Load the basic construction */
    Load(){
        this.played = false;
        this.listeners = new Array();
    }
    /** Init game method */
    Init(){
        this.attribute = new Game_Attr(snake_body_color, snake_head_color, food_color);
        this.state = new Game_State();
        //Init body
        for(let i = 1; i <= default_body_len; i++){
            let a = this.state.snake.head;
            this.state.snake.body_units.push(new Vec(((a.x - i >= 0)?(a.x - i):(map_width - Math.abs(a.x-i)%map_width)),a.y));
        }
    }
    /** Start the game */
    Play() {
        //create Game Interval
        this.createFood();
        this.interval = this.createInterval();
        this.renderer.render(this.state.snake, this.state.score, this.state.food_pos);
        this.addKeyListeners();
    }
    createInterval(){
        return setInterval(() => {
            this.moveSnake();
            this.renderer.render(this.state.snake, this.state.score, this.state.food_pos);
        }, difficulty);
    }
    moveSnake() {
        if(this.state.key_queue.length>0)this.state.direction = this.state.key_queue.shift(); //change
        let a = this.state.snake.head;
        this.state.snake.body_units.unshift(new Vec(this.state.snake.head.x, this.state.snake.head.y));
        if((a[move_key[this.state.direction].key]+move_key[this.state.direction].step)>=0)a[move_key[this.state.direction].key] = (a[move_key[this.state.direction].key]+move_key[this.state.direction].step)%20;
        else a[move_key[this.state.direction].key] = 20+(a[move_key[this.state.direction].key]+move_key[this.state.direction].step)%20;
        //碰撞检测
        if(this.state.snake.body_units.find(e=>e.equal(this.state.snake.head))){
            alert("Game Over! Your score is:"+ this.state.score);
            this.reload();
        }else if(this.state.snake.head.equal(this.state.food_pos)){
            this.state.score++;
            this.createFood();
        }else if(this.state.snake.body_units.length > 1)this.state.snake.body_units.pop();
    }
    createFood() {
        let pos;
        do {
            pos = new Vec(Math.floor(Math.random()*20), Math.floor(Math.random()*20));
        }while(this.state.snake.body_units.find(e=>e.equal(pos)));
        this.state.food_pos = pos;
    }
    addKeyListeners() {
        let a = (ev) => {
            if(prevent_key[(this.state.key_queue.length > 0)?(this.state.key_queue.at(-1)):(this.state.direction)] == direct_key[ev.key] || !direct_key[ev.key])return;
            this.state.key_queue.push(direct_key[ev.key]);
        };
        this.listeners.push(['keyup', a]);
        window.addEventListener('keyup', a);
    }
    reload(){
        clearInterval(this.interval);
        for(let i = 0; i < this.listeners.length; i++)window.removeEventListener(this.listeners[i][0], this.listeners[i][1]);
        this.Load();
        this.Init();
        this.Play();
    }
    //Other Methods (Maybe will be created in the future)
};

//Main Function
(function(){
    Game = new Game();
    Game.Init(); //init the game
    document.getElementById("start-button").addEventListener("mouseup", function(){
        document.getElementById("start-page").remove();
        document.getElementById("game-box").style.display = "block";
        Game.Play();
    })
}());
